package org.smartboot.flow.core.manager;

import org.smartboot.flow.core.attribute.AttributeValueResolver;
import org.smartboot.flow.core.common.ComponentType;
import org.smartboot.flow.core.common.Uniqueness;
import org.smartboot.flow.core.attribute.AttributeHolder;
import org.smartboot.flow.core.component.Component;
import org.smartboot.flow.core.metrics.Metrics;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author qinluo
 * @date 2022/11/18 21:08
 * @since 1.0.0
 */
@SuppressWarnings("rawtypes")
public class ComponentModel extends Uniqueness {

    private final Map<String, Object> holders = new ConcurrentHashMap<>();
    private Component component;
    private String name;
    private String describe;
    private final Map<String, ComponentModel> components = new ConcurrentHashMap<>();

    String executable;
    ComponentType type;
    PipelineModel pipeline;
    String condition;
    private String branch;
    private Metrics metrics;

    ComponentModel(ComponentType type, String identifier) {
        this.identifier = identifier;
        this.type = type;
    }

    public Map<String, Object> getHolders() {
        return holders;
    }

    public void addAttributes(List<AttributeHolder> attributes) {
        for (AttributeHolder holder : attributes) {
            this.holders.put(holder.getAttribute().getName(), holder.getValue());
        }
    }

    public String getExecutable() {
        return executable;
    }

    public ComponentType getType() {
        return type;
    }

    public PipelineModel getPipeline() {
        return pipeline;
    }

    public String getCondition() {
        return condition;
    }

    public String getBranch() {
        return branch;
    }

    public void setBranch(String branch) {
        this.branch = branch;
    }

    public Map<String, ComponentModel> getComponents() {
        return new HashMap<>(components);
    }

    Map<String, ComponentModel> collect() {
        if (type == ComponentType.PIPELINE) {
            Map<String, ComponentModel> collected = this.pipeline.collect();
            collected.put(this.identifier, this);
            return collected;
        } else if (type == ComponentType.BASIC) {
            Map<String, ComponentModel> temp = new HashMap<>(4);
            temp.put(this.identifier, this);
            return temp;
        } else {
            Map<String, ComponentModel> temp = new HashMap<>(8);
            this.components.forEach((k, v) -> temp.putAll(v.collect()));
            temp.put(this.identifier, this);
            return temp;
        }
    }

    void addComponent(ComponentModel model) {
        if (this.type == ComponentType.CHOOSE) {
            this.components.put(model.getBranch(), model);
        } else if (this.type == ComponentType.IF) {
            String key = this.components.size() > 0 ? "else" : "then";
            this.components.put(key, model);
        } else {
            this.components.put(model.getIdentifier(), model);
        }
    }

    public String getName() {
        return name;
    }

    public String getDescribe() {
        return describe;
    }

    @SuppressWarnings("unchecked")
    public void changeAttributes(List<AttributeHolder> holders) {
        Map<String, Object> snapshot = new HashMap<>(this.holders);
        AttributeValueResolver valueResolver = AttributeValueResolver.getInstance();

        try {
            holders.forEach(p -> {
                Object resolvedValue = valueResolver.resolve(p.getAttribute(), p.getValue());
                p.getAttribute().apply(component, resolvedValue);
            });
            addAttributes(holders);
        } catch (Exception e) {
            this.holders.clear();
            this.holders.putAll(snapshot);
        }

    }

    public Metrics getMetrics() {
        return metrics;
    }

    public void reset() {
        this.metrics.reset();
        components.forEach((k, v) -> v.reset());
    }

    public <T, S> void setComponent(Component<T, S> component) {
        this.component = component;
        this.name = component.getName();
        this.describe = component.describe();
        this.metrics = component.getMetrics();
    }
}
